/*
 * Copyright (c) 2011, Yawning <yawninglol at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.GeneticOptimizer;

import java.util.ArrayList;
import java.util.Arrays;

import org.apache.commons.math.genetics.Chromosome;
import org.apache.commons.math.genetics.ChromosomePair;
import org.apache.commons.math.genetics.CrossoverPolicy;

import yawning.mew.GeneticOptimizer.Optimizer.CandidateStat;


public class MutatingCrossover implements CrossoverPolicy {
	private Optimizer mOptimizer;
	private double mHitExpRate;
	private double mMultiRate;

	public MutatingCrossover(Optimizer opt, double multiRate, double hitExpRate) {
		mOptimizer = opt;
		mHitExpRate = hitExpRate;
		mMultiRate = multiRate;
	}

	@Override
	public ChromosomePair crossover(Chromosome arg0, Chromosome arg1) {
		GOChromosome first = (GOChromosome) arg0;
		GOChromosome second = (GOChromosome) arg1;

		return new ChromosomePair(mutateChromosome(first), mutateChromosome(second));
	}

	private GOChromosome mutateChromosome(GOChromosome chromo) {
		// Randomly shift stats around.
		int statVector[];

		do {
			ArrayList<CandidateStat> statsList = new ArrayList<CandidateStat>(mOptimizer.mCandidateStats);
			statVector = Arrays.copyOf(chromo.mStatDeltas, chromo.mStatDeltas.length);

			do {
				int nrStats = statsList.size();

				int fromStatIdx, toStatIdx;
				do {
					fromStatIdx = mOptimizer.mRng.nextInt(nrStats);
					toStatIdx = mOptimizer.mRng.nextInt(nrStats);
				} while (fromStatIdx == toStatIdx);

				CandidateStat fromStat = statsList.get(fromStatIdx);
				CandidateStat toStat = statsList.get(toStatIdx);

				if ((fromStat == CandidateStat.HIT_RATING || fromStat == CandidateStat.EXPERTISE_RATING) && (toStat == CandidateStat.HIT_RATING || toStat == CandidateStat.EXPERTISE_RATING)) {
					// Shifting stuff between hit/expertise is usually a waste of god damn time, unless you're a tank.
					if (!mOptimizer.mRng.nextBoolean(mHitExpRate)) {
						int oldIdx = toStatIdx;
						if (mOptimizer.mNrStats == 2) throw new RuntimeException("Need to specify more than just Hit/Expertise as candidate stats.");	// Seriously, do this shit by hand.
						do {
							toStatIdx = mOptimizer.mRng.nextInt(nrStats);
						} while (toStatIdx == oldIdx);
						toStat = statsList.get(toStatIdx);
					}
				}

				// FIXME: Ew ew ew ew ew ew ew ew ew.
				fromStatIdx = mOptimizer.mCandidateStats.indexOf(fromStat);
				toStatIdx = mOptimizer.mCandidateStats.indexOf(toStat);
				statsList.remove(fromStat);
				statsList.remove(toStat);

				int shiftAmt;
				if (statVector[fromStatIdx] == 0) {
					// Well, can't actually subtract out of this, so try to find a valid source/destination again.
					statsList.add(fromStat);
					statsList.add(toStat);
					continue;
				} else if (statVector[fromStatIdx] < 100) {
					// Use a bit of "intelligent design" to push things toward convergence.
					// If the value is low, it's likely a dump stat, so be more aggressive about trying to get rid of it.
					shiftAmt = statVector[fromStatIdx];
				} else {
					shiftAmt = mOptimizer.mRng.nextInt(statVector[fromStatIdx]) + 1;
				}

				statVector[fromStatIdx] -= shiftAmt;
				statVector[toStatIdx] += shiftAmt;

//				System.out.println("Shifted " + shiftAmt + " From: " + fromStat + " to " + toStat);

			} while (statsList.size() > 1 && mOptimizer.mRng.nextBoolean(mMultiRate));

			// Check again to see if we just derped around and ended up with something that has hit/exp being the only differences.
			if (mHitExpRate < 1.0 && mOptimizer.mCandidateStats.contains(CandidateStat.HIT_RATING) && mOptimizer.mCandidateStats.contains(CandidateStat.EXPERTISE_RATING)) {
				if (mOptimizer.mNrStats == 2) throw new RuntimeException("Need to specify more than just Hit/Expertise as candidate stats.");	// Seriously, do this shit by hand.

				int hitIdx = mOptimizer.mCandidateStats.indexOf(CandidateStat.HIT_RATING);
				int expIdx = mOptimizer.mCandidateStats.indexOf(CandidateStat.EXPERTISE_RATING);
				if (statVector[hitIdx] + statVector[expIdx] == chromo.mStatDeltas[hitIdx] + chromo.mStatDeltas[expIdx]) {
					// Well, hit + exp is identical, that's not good, did anything else change?
					boolean wasDifferent = false;
					for (int i = 0; i < mOptimizer.mNrStats; i++) {
						if (i != hitIdx && i != expIdx && statVector[i] != chromo.mStatDeltas[i]) wasDifferent = true;
					}
					if (!wasDifferent && statVector[hitIdx] == chromo.mStatDeltas[hitIdx] && statVector[expIdx] == chromo.mStatDeltas[expIdx]) continue;	// Herp.
					if (!wasDifferent && !mOptimizer.mRng.nextBoolean(mHitExpRate)) continue;	// Derp.
				}
			}

			return new GOChromosome(mOptimizer, statVector);

		} while (true);

	}
}
